/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package udpserver;

import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Server extends Thread {

    static final byte SIGN = 0;
    static final byte NICK = 1;
    static final byte UNSIGN = 2;
    static final byte MSG = 3;
    static final byte ID = 4;
    static final byte CLIENTS = 5;
    static final byte MULTICAST = 6;
    private static final String GROUP_HOST = "228.5.6.7";

    static final int PORT = 1234;  // port for this server
    private DatagramChannel udpChannel;
    private ByteBuffer inBuffer,  outBuffer;
    ArrayList<Client> clients;
    int lastID = 0;

    // for communication with the multicast group
    private MulticastSocket groupSock;
    private InetAddress groupAddr;
    private static final int GROUP_PORT = 5555;

    public Server() {
        try {
            udpChannel = DatagramChannel.open();
            udpChannel.socket().bind(new InetSocketAddress(PORT));
            udpChannel.configureBlocking(true);//metoda recieve zablokuje vlakno ve kterem je spustena
            inBuffer = ByteBuffer.allocateDirect(512); //alokovat buffer
            outBuffer = ByteBuffer.allocateDirect(512);
        } catch (IOException ex) {
            System.out.println("chyba pri vytvareni udp datagram channelu");
        }
        clients = new ArrayList<Client>();

        joinMulticastGroup();

        this.start();
    }

    private void joinMulticastGroup() {
        try {
            groupSock = new MulticastSocket(GROUP_PORT);
            System.out.println("Multicast socket created");
            groupAddr = InetAddress.getByName(GROUP_HOST); 
            groupSock.joinGroup(groupAddr);
            System.out.println("Multicast group joined");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public Client getClientByID(int id) {
        for (Iterator<Client> iter = clients.iterator(); iter.hasNext();) {
            Client client = iter.next();
            if (client.id == id) {
                return client;
            }
        }
        return null;
    }

    public void sendID(SocketAddress clientAddress, int id) {
        outBuffer.clear();
        outBuffer.put(ID);
        outBuffer.putInt(id);
        outBuffer.flip();

        System.out.println("SERVER: sending id " + id);
        try {
            udpChannel.send(outBuffer, clientAddress);
        } catch (IOException e) {
            System.out.println("chyba pri posilani nicku");
            e.printStackTrace(System.err);
        }
    }

    public void sendGroupAddress(SocketAddress clientAddress) {
        outBuffer.clear();
        outBuffer.put(MULTICAST);
        outBuffer.put(GROUP_HOST.getBytes());
        outBuffer.flip();
        try {
            udpChannel.send(outBuffer, clientAddress);
        } catch (IOException e) {
            System.out.println("chyba pri posilani nicku");
            e.printStackTrace(System.err);
        }
    }

    public void multicastClientsList(SocketAddress clientAddress) {
        byte[] data = {CLIENTS};
        String msg = new String(data);
        for (Iterator<Client> iter = clients.iterator(); iter.hasNext();) {
            Client client = iter.next();
            msg = msg + (client.nick + "\n");
        }
        System.out.println("Client list: " + msg);
        multicastPacket(msg);
    }

    private void multicastPacket(String msg) {
        try {
            DatagramPacket sendPacket =
                    new DatagramPacket(msg.getBytes(), msg.length(), groupAddr, GROUP_PORT);
            groupSock.send(sendPacket);
        } catch (IOException ioe) {
            System.out.println(ioe);
        }
    }

    private void multicastPacket(byte[] msg) {
        try {
            DatagramPacket sendPacket =
                    new DatagramPacket(msg, msg.length, groupAddr, GROUP_PORT);
            groupSock.send(sendPacket);
        } catch (IOException ioe) {
            System.out.println(ioe);
        }
    }

    public void broadcastMessage(int id, String msg) {
        outBuffer.clear();
        outBuffer.put(MSG);
        outBuffer.putInt(id);
        outBuffer.put((clients.get(id).nick + ": ").getBytes());//pred zpravu pridame nick
        outBuffer.put(msg.getBytes());
        outBuffer.flip();

        System.out.println("SERVER: broadcast message from id " + id);
        System.out.println("buffer: " + outBuffer.toString());
        try {
            for (Iterator<Client> iter = clients.iterator(); iter.hasNext();) {
                Client client = iter.next();
                udpChannel.send(outBuffer, client.clientAddress);
                outBuffer.rewind();
            }
        } catch (IOException e) {
            System.out.println("chyba pri posilani nicku");
            e.printStackTrace(System.err);
        }
    }

    @Override
    public void run() {
        byte[] buf;
        int id;
        SocketAddress clientAddress;
        try {

            while (true) {
                inBuffer.clear();
                System.out.println("SERVER: waiting for packet");
                clientAddress = udpChannel.receive(inBuffer); //přijmout UDP paket
                inBuffer.flip();
                //System.out.println("recieved packet " + inBuffer.toString());

                switch (inBuffer.get()) {//get automaticky presune aktualni pozici o 1
                    case SIGN: // pripojeni noveho klienta
                        System.out.println("server: SIGN");
                        clients.add(new Client(lastID, clientAddress, "unknown"));
                        sendID(clientAddress, lastID);
                        lastID++;
                        break;

                    case NICK: // pripojeni noveho klienta
                        System.out.println("server: NICK");
                        id = inBuffer.getInt();

                        //prevod zbytku ByteBufferu na String
                        buf = new byte[inBuffer.remaining()];
                        inBuffer.get(buf);
                        String nick = new String(buf);
                        System.out.println("nick id " + id);

                        System.out.println("string " + nick);
                        getClientByID(id).nick = nick;
                        break;

                    case MSG:
                        System.out.println("server: MSG");
                        id = inBuffer.getInt();

                        //prevod zbytku ByteBufferu na String
                        buf = new byte[inBuffer.remaining()];
                        inBuffer.get(buf);
                        String msg = new String(buf);

                        System.out.println("client id " + id + " msg " + msg);
                        broadcastMessage(id, msg);
                        break;

                    case UNSIGN:
                        System.out.println("server: UNSIGN");
                        id = inBuffer.getInt();
                        clients.remove(getClientByID(id));
                        System.out.println("client with id " + id + " removed");
                        System.out.println("number of remaining clients: " + clients.size());
                        break;
                    case MULTICAST:
                        System.out.println("server: MULTICAST");
                        sendGroupAddress(clientAddress);
                        multicastClientsList(clientAddress);
                        break;
                    case CLIENTS:
                        System.out.println("server: CLIENTS");
                        multicastClientsList(clientAddress);
                        break;
                }

            }

        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public static void main(String[] args) {
        Server server = new Server();
    }
}
